package com.nowcoder.community.config;

import com.nowcoder.community.filter.KaptchaFilter;
import com.nowcoder.community.service.UserService;
import com.nowcoder.community.util.CommunityConstant;
import com.nowcoder.community.util.CommunityUtil;
import com.nowcoder.community.util.CusAuthenticationManager;
import com.nowcoder.community.util.HostHolder;
import jakarta.servlet.http.HttpSession;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.GenericApplicationListenerAdapter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.context.DelegatingApplicationListener;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.session.*;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.UUID;

/**
 * @author szq
 * @create 2023-02-27 22:36
 * @description 自定义 Security 相关配置
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig implements CommunityConstant {

    private final AuthenticationConfiguration authenticationConfiguration;

    private final UserDetailsService userDetailsService;

    private final DataSource dataSource;

    private final DelegatingApplicationListener delegatingApplicationListener;

    private final RedisTemplate redisTemplate;

    private final UserService userService;

    private final CusAuthenticationManager cusAuthenticationManager;

    private final HostHolder hostHolder;

    public WebSecurityConfig(AuthenticationConfiguration authenticationConfiguration, UserDetailsService userDetailsService, DataSource dataSource, DelegatingApplicationListener delegatingApplicationListener, RedisTemplate redisTemplate, UserService userService, CusAuthenticationManager cusAuthenticationManager, HostHolder hostHolder) {
        this.authenticationConfiguration = authenticationConfiguration;
        this.userDetailsService = userDetailsService;
        this.dataSource = dataSource;
        this.delegatingApplicationListener = delegatingApplicationListener;
        this.redisTemplate = redisTemplate;
        this.userService = userService;
        this.cusAuthenticationManager = cusAuthenticationManager;
        this.hostHolder = hostHolder;
    }

    /*@Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }*/

    @Bean
    public RememberMeServices rememberMeServices() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        return new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(), userDetailsService, jdbcTokenRepository);
    }

    @Bean
    public SessionAuthenticationStrategy sessionAuthenticationStrategy() {

        SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
        ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy =
                new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
        delegatingApplicationListener.addListener(new GenericApplicationListenerAdapter(sessionRegistry));
        concurrentSessionControlStrategy.setMaximumSessions(1);
        //除非先在以前的客户端登出，否则不能在新的客户端登录
        //concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(false);
        //要注意这3个实例在集合中的顺序
        CompositeSessionAuthenticationStrategy delegateStrategies = new CompositeSessionAuthenticationStrategy(
                Arrays.asList(concurrentSessionControlStrategy,
                        new ChangeSessionIdAuthenticationStrategy(),
                        new RegisterSessionAuthenticationStrategy(sessionRegistry)
                ));
        return delegateStrategies;
    }

    @Bean
    public KaptchaFilter kaptchaFilter() throws Exception {
        KaptchaFilter kaptchaFilter = new KaptchaFilter(redisTemplate, userService);
        // 设置处理登录请求的url
        kaptchaFilter.setFilterProcessesUrl("/doLogin");
        // 设置表单参数名
        kaptchaFilter.setUsernameParameter("username");
        kaptchaFilter.setPasswordParameter("password");
        kaptchaFilter.setKaptchaParameter("code");

        kaptchaFilter.setRememberMeServices(rememberMeServices());
        kaptchaFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
        // 指定认证管理器
        kaptchaFilter.setAuthenticationManager(cusAuthenticationManager);
        // 指定认证成功处理
        kaptchaFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
            response.sendRedirect(request.getContextPath() + "/index");
        });
        // 指定认证失败处理
        kaptchaFilter.setAuthenticationFailureHandler((request, response, exception) -> {
            System.out.println(exception.getClass());
            System.out.println(exception.getMessage());
            HttpSession session = request.getSession(false);
            if (session != null) {
                session.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
            }
            response.sendRedirect(request.getContextPath() + "/login");
        });
        /*
        class com.nowcoder.community.exception.KaptchaNotMatchException
        验证码不匹配!
        class org.springframework.security.core.userdetails.UsernameNotFoundException
        账号不存在!
        class org.springframework.security.authentication.BadCredentialsException
        密码不正确!
         */

        kaptchaFilter.setSecurityContextRepository(securityContextRepository());
        return kaptchaFilter;
    }

    @Bean
    public HttpSessionSecurityContextRepository securityContextRepository() {
        return new HttpSessionSecurityContextRepository();
    }


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .requestMatchers("/css/**", "/js/**", "/user/header/**").permitAll()
                .requestMatchers("/doLogin", "/index", "/", "/login", "/register", "/activation/**", "/kaptcha", "/logout").permitAll();

        // 登录
        http.formLogin()
                .loginPage("/login");
        //.loginProcessingUrl("/doLogin")
                /*.successHandler((request, response, authentication) -> {
                    System.out.println("指定认证成功处理========================");
                    User user = (User) authentication.getPrincipal();
                    hostHolder.setUser(user);
                    response.sendRedirect(request.getContextPath() + "/index");
                })*/
        //.defaultSuccessUrl("/index")
        //.failureForwardUrl("/login");

        // 注销
        http.logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler((request, response, authentication) -> {
                    hostHolder.removeUser();
                    response.sendRedirect(request.getContextPath() + "/login");
                })
                .clearAuthentication(true)      // 默认清除认证标记
                .invalidateHttpSession(true);   // 默认会话失效

        // 授权
        http.authorizeHttpRequests()
                .requestMatchers(
                        "/user/setting",
                        "user/upload",
                        "/discuss/add",
                        "/letter/**",
                        "/notice/**",
                        "/comment/add/**",
                        "/like",
                        "/follow",
                        "/unfollow"
                )
                .hasAnyAuthority(
                        AUTHORITY_USER,
                        AUTHORITY_ADMIN,
                        AUTHORITY_MODERATOR
                )
                .requestMatchers(
                        "/discuss/top",
                        "/discuss/refine"
                ).hasAnyAuthority(
                        AUTHORITY_MODERATOR
                ).requestMatchers(
                        "/discuss/delete",
                        "/data/**"/*,
                        "/actuator/**"*/
                )
                .hasAnyAuthority(
                        AUTHORITY_ADMIN
                )
                .anyRequest().permitAll()
                .and().csrf().disable();

        // 权限不足时的处理
        http.exceptionHandling()
                // 用户未经登录就访问需认证接口时的处理逻辑
                .authenticationEntryPoint((request, response, authException) -> {
                    // 异步请求返回json,浏览器发出请求返回HTML
                    String xRequestedWith = request.getHeader("x-requested-with");
                    if ("XMLHttpRequest".equals(xRequestedWith)) {
                        response.setContentType("application/plain;charset=utf-8");// 普通文本
                        PrintWriter writer = response.getWriter();
                        writer.write(CommunityUtil.getJSONString(HttpStatus.UNAUTHORIZED.value(), "你还没有登录!"));
                    } else {
                        response.sendRedirect(request.getContextPath() + "/login");
                    }
                })
                // 用户登录成功后，但是不具备对应角色权限
                .accessDeniedHandler((request, response, accessDeniedException) -> {
                    // 异步请求返回json,浏览器发出请求返回HTML
                    String xRequestedWith = request.getHeader("x-requested-with");
                    if ("XMLHttpRequest".equals(xRequestedWith)) {
                        response.setContentType("application/plain;charset=utf-8");// 普通文本
                        PrintWriter writer = response.getWriter();
                        writer.write(CommunityUtil.getJSONString(HttpStatus.FORBIDDEN.value(), "你没有访问此功能的权限!"));
                    } else {
                        response.sendRedirect(request.getContextPath() + "/denied");
                    }
                });

        // 增加 Filter，处理验证码
        http.addFilterAt(kaptchaFilter(), UsernamePasswordAuthenticationFilter.class);

        // 记住我功能
        http.rememberMe()
                .rememberMeParameter("remember-me")
                //.tokenValiditySeconds()
                .rememberMeServices(rememberMeServices());

        return http.build();
    }
}
